package com.gitee.feizns.quickstart.mybatis.plus.plugins.inner.autofill;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.gitee.feizns.dynamic.Objs;
import com.gitee.feizns.dynamic.reflect.Constructors;
import com.gitee.feizns.dynamic.reflect.Types;
import com.gitee.feizns.quickstart.mybatis.plus.plugins.inner.autofill.annotation.AutoFill;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * mybatis-plus自动填充字段插件
 * <p>
 *     该类配置了一个支持了元注解{@link AutoFill}来支持填充自定义填充规则{@link AutoFillStrategy}
 * </p>
 * <p>
 *     在自定义mapper的情况 默认的{@link MetaObjectHandler}
 * </p>
 * @link <a href="https://baomidou.com/pages/4c6bcf/">mybatis-plus自动填充功能</a>
 * @author feizns
 * @since 2022/11/23
 */
public class AutoFillAnnotationInnerInterceptor implements InnerInterceptor {

    /**
     * 在更新之前拦截参数并填充参数
     * @param executor 遗嘱执行人
     * @param ms {@link MappedStatement}
     * @param parameter 参数
     */
    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) {
        SqlCommandType commandType = ms.getSqlCommandType();
        if ( commandType == SqlCommandType.INSERT || commandType == SqlCommandType.UPDATE ) {
            //如果是map填充所有的值、如果是集合的拆开处理
            if ( parameter instanceof Map )
                ((Map<?, ?>) parameter).values().forEach(obj -> fill(obj, commandType));
            else if ( parameter != null ) {
                Arrays.stream(Objs.toArray(parameter))
                        .filter(param -> Types.isJavaBeanType(param.getClass()))
                        .forEach(param -> fill(param, commandType));
            }
        }
    }

    /**
     * 填充字段
     * @param object 对象
     * @param commandType 命令类型
     */
    public void fill(Object object, SqlCommandType commandType) {
        if ( object != null && TableInfoHelper.getTableInfo(object.getClass()) != null ) {
            MetaObject metaObject = SystemMetaObject.forObject(object);
            if ( commandType == SqlCommandType.INSERT )
                fill(metaObject, FieldFill.INSERT, FieldFill.INSERT_UPDATE);
            else if ( commandType == SqlCommandType.UPDATE )
                fill(metaObject, FieldFill.UPDATE, FieldFill.INSERT_UPDATE);
        }
    }

    /**
     * 填充字段
     * @param metaObject 元对象
     * @param fieldFill 字段填满
     */
    public void fill(MetaObject metaObject, FieldFill... fieldFill) {
        handleFields(metaObject, field -> {
            TableField annotation = field.getAnnotation(TableField.class);
            if ( annotation != null )
                return Arrays.stream(fieldFill).anyMatch(tmp -> tmp == annotation.fill());
            return false;
        });
    }

    /**
     * 处理每个字段
     * @param metaObject 元对象
     * @param fieldPredicate 字段验证
     */
    private void handleFields(MetaObject metaObject, Predicate<Field> fieldPredicate) {
        List<Field> fields = TableInfoHelper.getAllFields(metaObject.getOriginalObject().getClass());
        fields.stream().filter(fieldPredicate).forEach(field -> {
            List<AutoFillStrategy> autoFillStrategies = getAuoFillFromFieldOrAnnotation(field);
            autoFillStrategies.forEach(strategy -> strategy.fill(field.getName(), metaObject));
        });
    }

    /**
     * 从字段或者从字段的注解上查找{@link AutoFill}
     * @param field {@link Field}
     * @return {@link AutoFill}
     */
    private List<AutoFillStrategy> getAuoFillFromFieldOrAnnotation(Field field) {
        if ( !autoFillsCache.containsKey(field) ) {
            List<Class<? extends AutoFillStrategy>> autoFills = new ArrayList<>();
            //先查找自定义的标注了@AutoFill的注解
            for (Annotation annotation : field.getAnnotations()) {
                if ( annotation.annotationType().isAnnotationPresent(AutoFill.class) )
                    autoFills.add(annotation.annotationType().getAnnotation(AutoFill.class).value());
            }
            //在查找字段上的@AutoFill
            if ( field.isAnnotationPresent(AutoFill.class) )
                autoFills.add(field.getAnnotation(AutoFill.class).value());
            autoFillsCache.put(field, autoFills.stream().map(Constructors::newInstance).collect(Collectors.toList()));
        }
        return autoFillsCache.get(field);
    }

    /**
     * 缓存字段上标注的字段填充策略
     */
    private static final Map<Field, List<AutoFillStrategy>> autoFillsCache = new ConcurrentHashMap<>();

}
